﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;

namespace MulticastTV
{
    /// <summary>
    /// Save whatever we receive from multicast sender to file
    /// FIXME !!!
    /// DOESN'T WORK GOOD. Some packets are dropped.
    /// </summary>
    class DumpToFile
    {
        static int TS_PACKET_SIZE = 188;
        string ip;
        int port;
        string file;
        Socket sock;
        Thread th;
        int timeout = 5000;
        volatile bool stopCondition = false;

        public void start(string Ip, string File)
        {
            if (Ip.Contains(":"))
            {
                stopCondition = false;
                ip = Ip.Split(":".ToCharArray())[0];
                int.TryParse(Ip.Split(":".ToCharArray())[1], out port);
                file = File;
                th = new Thread(new ThreadStart(receive));
                th.IsBackground = true;
                th.Start();
            }
        }

        public void stop()
        {
            stopCondition = true;            
        }
        
        private void receive()
        {
            int RECV_SIZE = 1500; 
            byte[] data = new byte[RECV_SIZE]; 
            int recv = 0;

            IPAddress mip;
            if (!IPAddress.TryParse(ip, out mip))
                return;

            IPEndPoint multiep = new IPEndPoint(mip, port);
            EndPoint epmulti = (EndPoint)multiep;
            IPEndPoint epinterf = new IPEndPoint(IPAddress.Parse(Globals.interfaceip), port);

            sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

            // bind socket
            try
            {
                sock.Bind(epinterf);
            }
            catch (SocketException ex)
            {
                if (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
                {
                    if (sock != null)
                    {
                        sock.Close();
                        sock = null;
                    }
                    return;
                }
            }
            sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, timeout);
            sock.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, IPAddress.Parse(Globals.interfaceip).GetAddressBytes());

            try
            {
                // Must be valid multicast address else exception 10049
                // Here we actually send IGMP packets
                sock.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(mip, IPAddress.Parse(Globals.interfaceip)));
            }
            catch (SocketException ex)
            {
                if (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
                {
                    if (sock != null)
                    {
                        sock.Close();
                        sock = null;
                    }
                    return;
                }
            }
            // start receiving
            try
            {
                recv = sock.ReceiveFrom(data, data.Length, SocketFlags.None, ref epmulti);
           
            }
            catch (SocketException ex1)
            {
                // nothing after 5 seconds ?
                if (ex1.SocketErrorCode == SocketError.TimedOut)
                {
                    if (sock != null)
                    {
                        sock.Close();
                        sock = null;
                    }
                    recv = 0;
                    return;
                }
            }

            // open file stream
            FileStream fs = null; ;
            try
            {
                fs = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Write,4096,true);
            }
            catch 
            {
                if (sock != null)
                {
                    sock.Close();
                    sock = null;
                }
                if (fs != null)
                {
                    fs.Close();
                }
            }

            byte[] syncbyte = BitConverter.GetBytes(0x47); //0x47
            byte[] buffer = new byte[RECV_SIZE];
                      
            while (recv > 0)
            {
                //ts packet is 188 bytes long
                //each udp packet should start with with syncbyte 0X47 
                //and is 188 x 7 =1316 bytes long
                
                WriteObject wo = new WriteObject();
                wo.FStream = fs;

                int j=0;
                for (int i = 0; i < recv; i=i+188)
                {
                    if (data[i] == syncbyte[0])
                    {
                        
                        Array.Copy(data, i, buffer, j, 188);
                        j = j + 188;
                    }
                    else
                    {
                        Console.Write("Syncbyte not found");
                    }
                }
                
                IAsyncResult asyncResult;
                asyncResult = fs.BeginWrite(buffer, 0, j, new AsyncCallback(EndWriteCallBack), wo);

                try
                {
                      recv = sock.ReceiveFrom(data,data.Length,SocketFlags.None,ref epmulti);
                }
                catch
                {
                    break;
                }
            
                if (stopCondition)
                    break;
            }

            // clean
            if (sock != null)
            {
                sock.Close();
                sock = null;
            }
            if (fs != null)
            {
                fs.Close();
            }
        }

        private void EndWriteCallBack(IAsyncResult res)
        {
            WriteObject wo = (WriteObject)res.AsyncState;
            FileStream ftemp = wo.FStream;
            ftemp.EndWrite(res);
        }
   }

    class WriteObject
    {
        FileStream fStream;
        
        public FileStream FStream
        {   get { return fStream; }
            set { fStream = value; }
        }
    }
}
